//
// Created by wxd on 2025/7/11.
// 封装着色器相关的工具类
//

#pragma once

#include "../../thirdParty/include/glad/glad.h"
#include <fstream>
#include <sstream>
#include <string>
#include <unordered_map>
#include <filesystem>
#include <stdexcept>
#include <tuple>


namespace fs = std::filesystem;

class ShaderUtils {
public:
    ShaderUtils(const ShaderUtils&) = delete;
    ShaderUtils& operator=(ShaderUtils&) = delete;

    ~ShaderUtils();

    /**
     * 缓存已经编译过的shader文件，避免重复编译问题
     */
    static std::unordered_map<std::string, GLuint> m_ShaderCacheMap;

    /**
     * 加载着色器
     * @param vertexPath:   顶点着色器路径
     * @param fragmentPath: 片元着色器路径
     * @return:             返回对应的程序寄存器id
     */
    static GLuint LoadShader(const std::string& vertexPath, const std::string& fragmentPath) {
        const std::string key = vertexPath + "|" + fragmentPath;
        if (auto const it = m_ShaderCacheMap.find(key); it != m_ShaderCacheMap.end()) {
            return it->second;
        }

        const std::string vertexCode = ReadFile(vertexPath);
        const std::string fragmentCode = ReadFile(fragmentPath);

        const GLuint program = CompileShader(vertexCode, fragmentCode);

        m_ShaderCacheMap[key] = program;
        return program;
    }

private:
    ShaderUtils();

    static std::string ReadFile(const std::string& path) {
        if (!fs::exists(path)) {
            throw std::runtime_error("Shader file not found: " + path);
        }

        std::ifstream file(path, std::ios::binary);
        file.exceptions(std::ios_base::badbit | std::ios_base::failbit);
        if (!file.is_open()) {
            throw std::runtime_error("Failed to open shader file: " + path);
        }

        std::ostringstream oss;
        oss << file.rdbuf();
        return oss.str();
    }

    /**
     * 编译单个 Shader（顶点或片段）
     */
    static GLuint CompileSingleShader(GLenum type, const std::string& source) {
        const GLuint shader = glCreateShader(type);
        const char* src = source.c_str();
        glShaderSource(shader, 1, &src, nullptr);
        glCompileShader(shader);

        // 检查编译错误
        GLint success;
        glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
        if (!success) {
            char infoLog[512];
            glGetShaderInfoLog(shader, 512, nullptr, infoLog);
            glDeleteShader(shader);
            throw std::runtime_error("Shader compilation failed: " + std::string(infoLog));
        }

        return shader;
    }

    /**
     * 编译并链接 Shader 程序
     */
    static GLuint CompileShader(const std::string& vertexCode, const std::string& fragmentCode) {
        const GLuint vertexShader = CompileSingleShader(GL_VERTEX_SHADER, vertexCode);
        const GLuint fragmentShader = CompileSingleShader(GL_FRAGMENT_SHADER, fragmentCode);

        const GLuint program = glCreateProgram();
        glAttachShader(program, vertexShader);
        glAttachShader(program, fragmentShader);
        glLinkProgram(program);

        GLint success;
        glGetProgramiv(program, GL_LINK_STATUS, &success);
        if (!success) {
            char infoLog[512];
            glGetProgramInfoLog(program, 512, nullptr, infoLog);
            glDeleteProgram(program);
            glDeleteShader(vertexShader);
            glDeleteShader(fragmentShader);
            throw std::runtime_error("Shader program linking failed: " + std::string(infoLog));
        }

        glDeleteShader(vertexShader);
        glDeleteShader(fragmentShader);

        return program;
    }
};

std::unordered_map<std::string, GLuint> ShaderUtils::m_ShaderCacheMap;
